<?php
class ubertemplate {
	var $template;
	var $values=array();
	var $blocks=array();
	var $generated='';
	
	/*********************************************************
	* addValues($values)
	* Adds $values to the template
	**/
	function addValues($values) {
		foreach($values as $name=>$value) {
			if(array_key_exists($name,$this->values))
				trigger_error('This template has duplicate value names',E_USER_ERROR);
			else
				$this->values[$name]=$value;
		}
	}
	
	/*********************************************************
	* addCode($string)
	* Processes $string in sequence, and adds component blocks to template
	**/
	function addCode($string) {
		//The start position and type of the first interesting block
		$start=strlen($string);
		$end=strlen($string);
		$name=false;
		$type=0; //0 - literal, 1 - boolean, 2 - inverse boolean, 3 - dynamic, 4 - variable, 5 - executable
		
		//Locate the next interesting block
		$executable=$this->nextBlock($string,'<~','>','</~','>');
		if($executable!==false&&$executable['startpos']<$start) {
			$start=$executable['startpos'];
			$end=$executable['endpos'];
			$name=$executable['tagname'];
			$type=5;
		}
		$variable=$this->nextTag($string,'<*','*>');
		if($variable!==false&&$variable['startpos']<$start) {
			$start=$variable['startpos'];
			$end=$variable['endpos'];
			$name=$variable['tagname'];
			$type=4;
		}
		$dynamic=$this->nextBlock($string,'<%','%>','</%','%>');
		if($dynamic!==false&&$dynamic['startpos']<$start) {
			$start=$dynamic['startpos'];
			$end=$dynamic['endpos'];
			$name=$dynamic['tagname'];
			$type=3;
		}
		$nboolean=$this->nextBlock($string,'<!','?>','</!','?>');
		if($nboolean!==false&&$nboolean['startpos']<$start) {
			$start=$nboolean['startpos'];
			$end=$nboolean['endpos'];
			$name=$nboolean['tagname'];
			$type=2;
		}
		$boolean=$this->nextBlock($string,'<','?>','</','?>');
		if($boolean!==false&&$boolean['startpos']<$start) {
			$start=$boolean['startpos'];
			$end=$boolean['endpos'];
			$name=$boolean['tagname'];
			$type=1;
		}
		
		//Now we check if the entire string is just a literal
		if($type==0) {
			$this->addLiteral($string);
			return;
		}
		//Then we check if the first block of the string is a literal
		elseif($start>0) {
			//Add a literal block, since we know where the first interesting tag starts
			$this->addLiteral(substr($string,0,$start));
			$end=$start;
		}
		//If not, add the respective block, without the original tags
		else {
			switch($type) {
				case 1:
					$this->addBoolean($name,substr($string,3+strlen($name),$end-7-strlen($name)*2));
				break;
				case 2:
					$this->addNBoolean($name,substr($string,4+strlen($name),$end-9-strlen($name)*2));
				break;
				case 3:
					$this->addDynamic($name,substr($string,4+strlen($name),$end-9-strlen($name)*2));
				break;
				case 4:
					$this->addVariable($name);
				break;
				case 5:
					$this->addExecutable($name,substr($string,6,$end-13));
				break;
				default:
					trigger_error('WTFBBQ',E_USER_ERROR);
			}
		}
		
		//Is there still some more to add?
		if($end<strlen($string)-1) {
			//Add the rest of the string
			$this->addCode(substr($string,$end));
		}
	}
	
	function addLiteral($string) {
		$this->blocks[]=new literal($string);
	}
	
	function addBoolean($name, $string) {
		$this->blocks[]=new positiveboolean($name, $string);
	}
	
	function addNBoolean($name, $string) {
		$this->blocks[]=new negativeboolean($name, $string);
	}
	
	function addDynamic($name, $string) {
		$this->blocks[]=new dynamic($name, $string);
	}
	
	function addVariable($name) {
		$this->blocks[]=new variable($name);
	}
	
	function addExecutable($name, $string) {
		$this->blocks[]=new executable($name, $string);
	}
	
	/*********************************************************
	* nextTag($string, $start, $end)
	* Locates and returns the first valid tag block
	**/
	function nextTag($string, $startstring, $endstring, $knownname=false,$offset=0) {
		$end_still_possible=true;
		//Find a start position
		$startpos=strpos($string,$startstring);
		//If there are no more start positions
		if($startpos===false)
			return false;
		//Find the first end position after the start pos
		$endpos=strpos($string,$endstring,$startpos+strlen($startstring));
		//If there are no more end posistions
		if($endpos===false)
			return false;
		//Get the tag name
		$tagname=substr($string,$startpos+strlen($startstring),$endpos-$startpos-strlen($startstring));
		//If this string isn't alphanumeric, search on
		if(($knownname!==false&&$tagname!==$knownname)||preg_replace('/[^a-zA-Z0-9_]/','',$tagname)!=$tagname) {
			return $this->nextTag(substr($string,$startpos+strlen($startstring)),$startstring,$endstring,$knownname,$offset+$startpos+strlen($startstring));
		}
		else {
			return array('tagname'=>$tagname,'namelength'=>strlen($tagname),'startpos'=>$offset+$startpos,'endpos'=>$offset+$endpos+strlen($endstring));
		}
	}
	
	/*********************************************************
	* nextBlock($string, $startstart, $startend, $endstart, $endend)
	* Locates and returns the first valid tag block
	**/
	function nextBlock($string, $startstart, $startend, $endstart, $endend) {
		$end_still_possible=true;
		$end_find_start=0;
		//Find a start tag
		$startpos=$this->nextTag($string,$startstart,$startend);
		if($startpos===false)
			return false;
		//Find the first end position after the start pos
		$endpos=$this->nextTag(substr($string,$startpos['endpos']),$endstart, $endend,$startpos['tagname']);
		if($endpos===false)
			return false;
		//Add the offset to the results
		$endpos['startpos']+=$startpos['endpos'];
		$endpos['endpos']+=$startpos['endpos'];
		return array('tagname'=>$startpos['tagname'],'startpos'=>$startpos['startpos'],'endpos'=>$endpos['endpos']);
	}
	
	/*********************************************************
	* process()
	* Evaluates the template
	**/
	function process() {
		$this->generated='';
		foreach($this->blocks as $blockname => $obj) {
			$result=$obj->get($this->values);
			$this->generated=$this->generated.$result;
		}
	}
	
	/*********************************************************
	* processBlockCode()
	* Evaluates the template
	**/
	function processBlockCode() {
		$this->generated='';
		foreach($this->blocks as $blockname => $obj) {
			if(is_a($obj,'dynamic')) {
				$result=$obj->get($this->values,true);
			}
			else {
				$result=$obj->get($this->values);
			}
			$this->generated=$this->generated.$result;
		}
	}
	
	/*********************************************************
	* setBlockCode()
	* Set the blockCode
	**/
	function setBlockCode($blockCode) {
		$this->blocks=unserialize($blockCode);
	}
	
	/*********************************************************
	* getBlockCode()
	* Returns generated blockCode
	**/
	function getBlockCode() {
		//Get existing blocks
		$blocks=$this->blocks;
		//Parse dynamics further
		foreach($blocks as $index=>$obj) {
			if(is_a($obj,'dynamic')) {
				$temp=new ubertemplate();
				$temp->addCode($obj->dynamic);
				$this->blocks[$index]->dynamic=$temp->getBlockCode();
			}
		}
		return serialize($this->blocks);
	}
	
	/*********************************************************
	* get()
	* Returns generated html
	**/
	function get() {
		return $this->generated;
	}
	
	/*********************************************************
	* write()
	* Prints generated html
	**/
	function write() {
		echo($this->generated);
	}
}

class block {
	var $generated='';
	
	function process(&$values){}
	
	/*********************************************************
	* get()
	* Get this block's value
	**/
	function get(&$values) {
		$this->process($values);
		return $this->generated;
	}
}

class literal extends block {
	
	/*********************************************************
	* literal($value)
	* Saves this value
	**/
	function literal($value) {
		$this->generated=$value;
	}
}

class variable extends block {
	
	/*********************************************************
	* variable($value)
	* Saves this value
	**/
	function variable($name) {
		$this->name=$name;
	}
	
	/*********************************************************
	* process($value)
	* Saves this value
	**/
	function process(&$value) {
		if(array_key_exists($this->name,$value))
			$this->generated=$value[$this->name];
		else
			$this->generated='';
	}
}

class dynamic extends block {
	
	/*********************************************************
	* dynamic($name, $dynamic)
	* Saves this dynamic's data
	**/
	function dynamic($name, $dynamic) {
		$this->name=$name;
		$this->dynamic=$dynamic;
	}
	
	/*********************************************************
	* get()
	* Get this block's value
	**/
	function get(&$values,$blockCode=false) {
		$this->process($values,$blockCode);
		return $this->generated;
	}
	
	/*********************************************************
	* process()
	* Processes this dynamic with the given values
	**/
	function process(&$values,$blockCode=false) {
		//Start with an empty dynamic
		$this->generated='';
		
		//Is there data to process?
		if(array_key_exists($this->name,$values)&&is_array($values[$this->name])&&count($values[$this->name])>0) {
			//Process each in a new template
			foreach($values[$this->name] as $itemid => $data) {
				$result=new ubertemplate;
				$result->addValues($data);
				if($blockCode) {
					$result->setBlockCode($this->dynamic);
					$result->processBlockCode();
				}
				else {
					$result->addCode($this->dynamic);
					$result->process();
				}
				$this->generated=$this->generated.$result->get();
			}
		}
	}
	
}

class positiveboolean extends block {
	
	/*********************************************************
	* positiveboolean($name, $internal)
	* Saves this boolean's name and internal code
	**/
	function positiveboolean($name,$internal) {
		$this->name=$name;
		$this->internal=$internal;
	}
	
	/*********************************************************
	* process()
	* Processes this boolean with the given values
	**/
	function process(&$values) {
		//Start with an empty result
		$this->generated='';
		//If this boolean evals to true
		if(array_key_exists($this->name,$values)&&$values[$this->name]===true) {
			//Process internals in a new template
			$result=new ubertemplate;
			$result->addCode($this->internal);
			$result->addValues($values);
			$result->process();
			$this->generated=$this->generated.$result->get();
		}
	}
}

class negativeboolean extends block {
	
	/*********************************************************
	* negativeboolean($name, internal)
	* Saves this boolean's name and internal code
	**/
	function negativeboolean($name,$internal) {
		$this->name=$name;
		$this->internal=$internal;
	}
	
	/*********************************************************
	* process()
	* Processes this negative boolean with the given values
	**/
	function process(&$values) {
		//Start with an empty result
		$this->generated='';
		//If this boolean evals to false
		if(array_key_exists($this->name,$values)&&$values[$this->name]===false) {
			//Process internals in a new template
			$result=new ubertemplate;
			$result->addCode($this->internal);
			$result->addValues($values);
			$result->process();
			$this->generated=$this->generated.$result->get();
		}
	}
}

class executable extends block {
	
	/*********************************************************
	* executable($name, internal)
	* Saves this executable's name and internal code
	**/
	function executable($name,$internal) {
		$this->name=$name;
		$this->internal=$internal;
	}
	
	/*********************************************************
	* process()
	* Processes this executable with the given values
	**/
	function process(&$values) {
		/*Eval code inside*/
		ob_start();
		eval($this->internal);
		$this->generated=ob_get_clean();
	}
}
?>